﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Compiler.TableOfSymbols;
using Compiler.TableOfSymbols.Types;
using Compiler.TableOfSymbols.Concepts;
using Compiler.CompilerAPI;
using Compiler.Unification.Constraints;

namespace Compiler.Unification.Auxiliary
{
    public class AuxiliaryUnifyAlgos
    {
        private CompilationEnvironment environment;
        private ConceptDefinition concept;

        public string ConceptTitle
        {
            get { return string.Format("Концепт {0}: ", concept.Name); }
        }

        public AuxiliaryUnifyAlgos(CompilationEnvironment environment, ConceptDefinition concept)
        {
            if (environment == null)
                throw new ArgumentNullException("environment can not be null");
            if (concept == null)
                throw new ArgumentNullException("concept can not be null");
            this.environment = environment;
            this.concept = concept;
        }

        public bool AddEqualError(BaseType a, BaseType b)
        {
            environment.ErrorManager.AddSemanticError(new UnificationError(
                String.Format(ConceptTitle + "Тип {0} нельзя приравнять к {1}",
                    environment.TypesBijectiveMap.GetTypeName(a),
                    environment.TypesBijectiveMap.GetTypeName(b))));
            return false;
        }

        public bool AddSubtypingError(BaseType a, BaseType b)
        {
            environment.ErrorManager.AddSemanticError(new UnificationError(
                String.Format(ConceptTitle + "Тип {0} не может быть подтипом {1}",
                    environment.TypesBijectiveMap.GetTypeName(a),
                    environment.TypesBijectiveMap.GetTypeName(b))));
            return false;
        }

        public bool AddDifferentChainsError(BaseType a, BaseType b)
        {
            environment.ErrorManager.AddSemanticError(new UnificationError(
                String.Format(ConceptTitle + "Типы {0} и {1} находятся в разных цепочках наследования",
                    environment.TypesBijectiveMap.GetTypeName(a),
                    environment.TypesBijectiveMap.GetTypeName(b))));
            return false;
        }

        private void _AddSametypeConstraints(Type[] args1, Type[] args2, ConstraintsQueue constraints)
        {
            System.Diagnostics.Debug.Assert(args1.Length == args2.Length,
                "args1 and args2 must be arguments of the same generic def");
            for (int i = 0; i < args1.Length; ++i)
            {
                if (!environment.TypesBijectiveMap.Contains(args1[i]))
                    environment.TypesBijectiveMap.AddPairByNETType(args1[i]);
                if (!environment.TypesBijectiveMap.Contains(args2[i]))
                    environment.TypesBijectiveMap.AddPairByNETType(args2[i]);
                constraints.Enqueue(new SametypeConstraint(
                    environment.TypesBijectiveMap[args1[i]],
                    environment.TypesBijectiveMap[args2[i]]));
            }
        }

        public bool AreEqual(TypeValue a, TypeValue b, ConstraintsQueue constraints, out bool constraintsAdded)
        {
            constraintsAdded = false;
            // сравниваем ссылки
            if (a == b)
                return true;
            if (a.IsTopType != b.IsTopType)
                return false;
            else if (a.IsBottomType != b.IsBottomType)
                return false;
            else if (a.IsTopType || a.IsBottomType)
                return true;
            // здесь a и b являются типами NET-классов
            ClassTypeValueNET a1 = a as ClassTypeValueNET;
            ClassTypeValueNET b1 = b as ClassTypeValueNET;
            System.Diagnostics.Debug.Assert(a1 != null,
                "a1 must be ClassTypeValueNET here");
            System.Diagnostics.Debug.Assert(b1 != null,
                "b1 must be ClassTypeValueNET here");
            return AreEqual(a1, b1, constraints, out constraintsAdded);
        }

        private bool AreEqual(ClassTypeValueNET a, ClassTypeValueNET b, ConstraintsQueue constraints, out bool constraintsAdded)
        {
            constraintsAdded = false;
            // простой тип не может быть равен никакому обобщённому
            if (a.IsSimpleType != b.IsSimpleType)
                return false;
            // если оба -- простые типы, их ссылки равны (и это уже должно быть проверено раньше)
            else if (a.IsSimpleType)
            {
                System.Diagnostics.Debug.Assert(a != b,
                    "Simple type refs must be checked earlier");
                return false;
            }
            // a и b -- обобщённые типы
            GenericClassTypeValue a1 = a as GenericClassTypeValue;
            GenericClassTypeValue b1 = b as GenericClassTypeValue;
            System.Diagnostics.Debug.Assert(a1 != null,
                "a1 must be GenericClassTypeValue here");
            System.Diagnostics.Debug.Assert(b1 != null,
                "b1 must be GenericClassTypeValue here");
            System.Diagnostics.Debug.Assert(!a1.IsGenericTypeDefinition,
                 "Generic definition a1 must not be in constraint");
            System.Diagnostics.Debug.Assert(!b1.IsGenericTypeDefinition,
                 "Generic definition b1 must not be in constraint");
            return AreEqual(a1 as ConstructedGenericType, b1 as ConstructedGenericType, constraints, out constraintsAdded);
        }

        private bool AreEqual(ConstructedGenericType a, ConstructedGenericType b, ConstraintsQueue constraints, out bool constraintsAdded)
        {
            constraintsAdded = false;
            System.Diagnostics.Debug.Assert(a != null,
                "a must be ConstructedGenericType here");
            System.Diagnostics.Debug.Assert(b != null,
                "b must be ConstructedGenericType here");
            // одинаковые закрытые сконструированные типы соответствуют одной ссылке,
            // это должно быть уже проверено
            if (a.IsGenericType && b.IsGenericType)
            {
                System.Diagnostics.Debug.Assert(a != b,
                    "Closed costructed type refs must be checked earlier");
                return false;
            }
            if (a.Definition != b.Definition)
                return false;
            else
            {
                Type aNET = environment.TypesBijectiveMap[a];
                Type bNET = environment.TypesBijectiveMap[b];
                _AddSametypeConstraints(aNET.GenericTypeArguments, bNET.GenericTypeArguments,
                    constraints);
                constraintsAdded = true;
                return true;
            }
        }

        public bool LeftIsSubtypeOrEqual(TypeValue a, TypeValue b, ConstraintsQueue constraints, out bool constraintsAdded)
        {
            constraintsAdded = false;
            System.Diagnostics.Debug.Assert(!(a is GenericTypeDefinition),
                 "Generic definition a must not be in constraint");
            System.Diagnostics.Debug.Assert(!(b is GenericTypeDefinition),
                 "Generic definition b must not be in constraint");
            // сравниваем ссылки
            if (a == b)
                return true;
            if (a.IsBottomType)
                return true;
            else if (b.IsTopType)
                return true;
            else if (a.IsTopType)
                return false;
            else if (b.IsBottomType)
                return false;
            // здесь a и b являются типами NET-классов
            ClassTypeValueNET a1 = a as ClassTypeValueNET;
            ClassTypeValueNET b1 = b as ClassTypeValueNET;
            System.Diagnostics.Debug.Assert(a1 != null,
                "a1 must be ClassTypeValueNET here");
            System.Diagnostics.Debug.Assert(b1 != null,
                "b1 must be ClassTypeValueNET here");
            return LeftIsSubtypeOrEqual(a1, b1, constraints, out constraintsAdded);
        }

        private bool LeftIsSubtypeOrEqual(ClassTypeValueNET a, ClassTypeValueNET b, ConstraintsQueue constraints, out bool constraintsAdded)
        {
            constraintsAdded = false;
            bool aIsSpec = a.IsSimpleType || (a.IsSomeGenericType && (a as ConstructedGenericType).IsGenericType);
            bool bIsSpec = b.IsSimpleType || (b.IsSomeGenericType && (b as ConstructedGenericType).IsGenericType);
            if (aIsSpec)
                if (bIsSpec)
                    return _LeftIsSubtypeOrEqualForSpecTypes(a, b, constraints, out constraintsAdded);
                else
                    return _LeftIsSubtypeOrEqualForRightGenVar(a, b as GenericVarType, constraints, out constraintsAdded);
            else
                return _LeftIsSubtypeOrEqualForLeftGenVar(a as GenericVarType, b, constraints, out constraintsAdded);
        }

        private bool _LeftIsSubtypeOrEqualForSpecTypes(ClassTypeValueNET a, ClassTypeValueNET b, ConstraintsQueue constraints, out bool constraintsAdded)
        {
            bool aIsSpec = a.IsSimpleType || (a.IsSomeGenericType && (a as ConstructedGenericType).IsGenericType);
            bool bIsSpec = b.IsSimpleType || (b.IsSomeGenericType && (b as ConstructedGenericType).IsGenericType);
            System.Diagnostics.Debug.Assert(aIsSpec, "a must be spec type here");
            System.Diagnostics.Debug.Assert(bIsSpec, "b must be spec type here");

            constraintsAdded = false;
            Type aNET = environment.TypesBijectiveMap[a];
            Type bNET = environment.TypesBijectiveMap[b];
            Type aNETBase = aNET;
            while (aNETBase != null)
            {
                if (aNETBase == bNET)
                    return true;
                //aNETBase = aNETBase.BaseType;
                aNETBase = _GetBaseType(aNETBase);
            }
            return false;
        }
        private bool _LeftIsSubtypeOrEqualForLeftGenVar(GenericVarType a, ClassTypeValueNET b, ConstraintsQueue constraints, out bool constraintsAdded)
        {
            System.Diagnostics.Debug.Assert(a != null, "a must be GenericVarType here");

            constraintsAdded = false;
            Type aNET = environment.TypesBijectiveMap[a];
            Type bNET = environment.TypesBijectiveMap[b];
            Type aNETDef = aNET.GetGenericTypeDefinition();
            System.Diagnostics.Debug.Assert(aNETDef != null, "aNET must have definition");
            Type bNETPossibleDef = (bNET.IsGenericType)
                ? bNET.GetGenericTypeDefinition() : null;
            Type aNETBase = aNET;
            while (aNETBase != null)
            {
                if (aNETBase == bNET)
                    return true;
                else if (aNETBase.IsGenericType
                    && (aNETBase.GetGenericTypeDefinition() == bNETPossibleDef))
                {
                    // bNETPossibleDef != null
                    _AddSametypeConstraints(aNETBase.GenericTypeArguments,
                        bNET.GenericTypeArguments, constraints);
                    constraintsAdded = true;
                    return true;
                }
                //aNETBase = aNETBase.BaseType;
                aNETBase = _GetBaseType(aNETBase);
            }
            return false;
        }
        private bool _LeftIsSubtypeOrEqualForRightGenVar(ClassTypeValueNET a, GenericVarType b, ConstraintsQueue constraints, out bool constraintsAdded)
        {
            bool aIsSpec = a.IsSimpleType || (a.IsSomeGenericType && (a as ConstructedGenericType).IsGenericType);
            System.Diagnostics.Debug.Assert(aIsSpec, "a must be spec type here");
            System.Diagnostics.Debug.Assert(b != null, "b must be GenericVarType here");

            constraintsAdded = false;
            Type aNET = environment.TypesBijectiveMap[a];
            Type bNET = environment.TypesBijectiveMap[b];
            Type aNETBase = aNET;
            Type bNETDef = bNET.GetGenericTypeDefinition();
            while (aNETBase != null)
            {
                if (aNETBase.IsGenericType
                    && aNETBase.GetGenericTypeDefinition() == bNETDef)
                {
                    _AddSametypeConstraints(aNETBase.GenericTypeArguments,
                        bNET.GenericTypeArguments, constraints);
                    constraintsAdded = true;
                    return true;
                }
                //aNETBase = aNETBase.BaseType;
                aNETBase = _GetBaseType(aNETBase);
            }
            return false;
        }

        private Type _GetBaseType(Type typeInfoNET)
        {
            Type baseTypeInfoNET = typeInfoNET.BaseType;
            if (baseTypeInfoNET == null)
                return baseTypeInfoNET;
            if (typeInfoNET.IsGenericType && typeInfoNET.ContainsGenericParameters
                && baseTypeInfoNET.IsGenericType && baseTypeInfoNET.ContainsGenericParameters)
            {
                // в этом случае базовый тип возвращается неверно 
                // Если обобщённый тип содержит типовые переменные, в качестве базового типа
                // возвращается базовый тип для его определения.
                // И если определение обобщённое, то всё плохо.
                Type[] argsNET = typeInfoNET.GenericTypeArguments;
                Type typeDefNET = typeInfoNET.GetGenericTypeDefinition();
                Type[] defArgsNET = typeDefNET.GetGenericArguments();
                Type[] typeDefBaseArgsNET = baseTypeInfoNET.GenericTypeArguments;
                // параметры базового типа (который является базовым типом определения typeInfoNET)
                // нужно заменить на реальные аргументы типа
                bool changed = false;
                for (int i = 0; i < typeDefBaseArgsNET.Length; ++i)
                {
                    int genArgInd = -1;
                    for (int j = 0; j < defArgsNET.Length; ++j)
                        if (defArgsNET[j] == typeDefBaseArgsNET[i])
                        {
                            genArgInd = j;
                            break;
                        }
                    if (genArgInd != -1)
                    {
                        typeDefBaseArgsNET[i] = argsNET[genArgInd];
                        changed = true;
                    }
                }
                if (changed)
                    baseTypeInfoNET = baseTypeInfoNET.GetGenericTypeDefinition().MakeGenericType(typeDefBaseArgsNET);
            }
            return baseTypeInfoNET;
        }
    }
}
